package com.seckill.service;

import com.seckill.comm.RedisKey;
import com.seckill.config.ZKConfig;
import com.seckill.entity.Book;
import com.seckill.entity.Order;
import com.seckill.entity.Result;
import com.seckill.exception.BizException;
import com.seckill.mapper.BookMapper;
import com.seckill.mapper.OrderMapper;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 1、第一轮纯数据库操作 QPS=346
 * 2、第二轮redis优化后 QPS=896
 * 3、第三轮引入JVM缓存 QPS=756
 * @author wangzhilong
 * @date 2019-06-18
 */
@Service
public class SeckillService {

    private Logger logger = LoggerFactory.getLogger(SeckillService.class);

    @Autowired
    private BookMapper bookMapper;

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private StringRedisTemplate redisTemplate ;

    private Map<Integer, Boolean> seckBook = new ConcurrentHashMap<>();

    @Autowired
    private ZooKeeper zooKeeper ;

    @PostConstruct
    public void initBook(){
        ValueOperations<String, String> operations = redisTemplate.opsForValue();
        List<Book> list = bookMapper.list();
        list.forEach(book -> operations.set(RedisKey.BOOK_SIZE+ book.getId(),book.getTotal().toString()));
    }

    @Transactional
    public Result seckill(Integer bookId) throws KeeperException, InterruptedException {

        try {
            if (seckBook.get(bookId) != null) {
                throw new BizException("121库存已经售完");
            }
            long count = decrementAndGet(bookId);
            if (count < 0) {
                seckBook.put(bookId, true);

                logger.info("========设置商品{}内存售完标记========",bookId);
                String path = getPath(bookId);
                Stat stat = zooKeeper.exists(path, true);
                if (stat == null) {
                    zooKeeper.create(path , "true".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
                }
                //存在值修改
                zooKeeper.setData(path, "true".getBytes(), -1);
                //监听售完节点标记
                zooKeeper.exists(path, true);

                throw new BizException("2库存已经售完");
            }
            //创建订单
            orderMapper.insert(new Order(bookId));
            //扣减库存
            Integer size = bookMapper.seckill(bookId);
            if (size < 1) {
                throw new BizException("3库存已经售完");
            }
        } catch (BizException e) {
            throw new RuntimeException(e.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
            incrementAndGet(bookId);
            seckBook.remove(bookId);
            //修改售完标记为false
            String path = getPath(bookId);
            if (zooKeeper.exists(path, true) != null) {
                zooKeeper.setData(path,"false".getBytes(),-1);
            }

            throw new RuntimeException(e.getMessage());
        }
        return Result.OK();
    }

    private String getPath(Integer bookId) {
        return ZKConfig.path + "/" + bookId;
    }

    private long decrementAndGet(Integer bookId){
        ValueOperations<String, String> operations = redisTemplate.opsForValue();
        return operations.decrement(RedisKey.BOOK_SIZE + bookId);
    }

    private long incrementAndGet(Integer bookId){
        ValueOperations<String, String> operations = redisTemplate.opsForValue();
        return operations.increment(RedisKey.BOOK_SIZE + bookId);
    }

    public void removeJvmCache(Integer bookId) {
        seckBook.remove(bookId);
    }

}
